/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.wsdl.loadtest.data;

import com.eviware.soapui.model.support.LoadTestRunListenerAdapter;
import com.eviware.soapui.model.support.TestSuiteListenerAdapter;
import com.eviware.soapui.model.testsuite.LoadTest;
import com.eviware.soapui.model.testsuite.LoadTestRunContext;
import com.eviware.soapui.model.testsuite.LoadTestRunner;
import com.eviware.soapui.model.testsuite.TestCase;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestStepResult;
import org.apache.log4j.Logger;

import javax.swing.table.AbstractTableModel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * TableModel holding loadtest samples
 */

public class SamplesModel extends AbstractTableModel {
    private final LoadTest loadTest;
    private List<TestSample[]> samples = new ArrayList<TestSample[]>();
    private InternalTestRunListener testRunListener;
    private InternalTestSuiteListener testSuiteListener;
    private InternalPropertyChangeListener propertyChangeListener;
    private TestCase testCase;
    private final static Logger log = Logger.getLogger(SamplesModel.class);

    public SamplesModel(LoadTest loadTest) {
        this.loadTest = loadTest;

        testRunListener = new InternalTestRunListener();
        testSuiteListener = new InternalTestSuiteListener();
        propertyChangeListener = new InternalPropertyChangeListener();

        testCase = loadTest.getTestCase();
        loadTest.addLoadTestRunListener(testRunListener);
        testCase.getTestSuite().addTestSuiteListener(testSuiteListener);

        for (TestStep testStep : testCase.getTestStepList()) {
            testStep.addPropertyChangeListener(TestStep.NAME_PROPERTY, propertyChangeListener);
        }
    }

    public int getRowCount() {
        return samples.size();
    }

    public int getColumnCount() {
        return testCase.getTestStepCount();
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        TestSample[] testSamples = samples.get(rowIndex);
        return testSamples == null ? "discarded" : testSamples[columnIndex];
    }

    public void addSamples(TestSample[] newSamples) {
        if (newSamples.length != getColumnCount()) {
            throw new RuntimeException("Invalid number of samples reported: " + newSamples.length + ", expected "
                    + getColumnCount());
        }

        samples.add(newSamples);

        fireTableRowsInserted(samples.size() - 1, samples.size() - 1);
    }

    public Class<?> getColumnClass(int columnIndex) {
        return TestSample.class;
    }

    public String getColumnName(int column) {
        return testCase.getTestStepAt(column).getName();
    }

    public void clear() {
        int size = samples.size();
        if (size > 0) {
            samples.clear();
            fireTableRowsDeleted(0, size);
        }
    }

    /**
     * Listener for collecting samples
     *
     * @author Ole.Matzura
     */

    private class InternalTestRunListener extends LoadTestRunListenerAdapter {
        public void afterTestCase(LoadTestRunner loadTestRunner, LoadTestRunContext context, TestCaseRunner testRunner,
                                  TestCaseRunContext runContext) {
            Map<TestStep, TestSample> samplesMap = new HashMap<TestStep, TestSample>();
            List<TestStepResult> results = testRunner.getResults();

            for (int c = 0; c < results.size(); c++) {
                TestStepResult result = results.get(c);
                if (result == null) {
                    log.warn("Result [" + c + "] is null in TestCase [" + testCase.getName() + "]");
                    continue;
                }

                TestStep testStep = result.getTestStep();

                if (!samplesMap.containsKey(testStep)) {
                    samplesMap.put(testStep, new TestSample(testStep));
                }

                samplesMap.get(testStep).addTestStepResult(result);
            }

            TestCase testCase = loadTest.getTestCase();

            TestSample[] samples = new TestSample[testCase.getTestStepCount()];
            for (int c = 0; c < samples.length; c++) {
                samples[c] = samplesMap.get(testCase.getTestStepAt(c));
            }

            addSamples(samples);
        }
    }

    public List<TestSample[]> getSamples() {
        return samples;
    }

    public void release() {
        loadTest.removeLoadTestRunListener(testRunListener);
        loadTest.getTestCase().getTestSuite().removeTestSuiteListener(testSuiteListener);

        for (TestStep testStep : loadTest.getTestCase().getTestStepList()) {
            testStep.removePropertyChangeListener(propertyChangeListener);
        }
    }

    /**
     * Holder for a TestSample
     *
     * @author ole.matzura
     */

    public static final class TestSample {
        private final TestStep testStep;
        private List<TestStepResult> results;

        public TestSample(TestStep testStep) {
            this.testStep = testStep;
        }

        public void addTestStepResult(TestStepResult result) {
            if (result.getTestStep() != testStep) {
                throw new RuntimeException("Trying to add sample for false testStep [" + result.getTestStep().getName()
                        + "], " + "expecting [" + testStep.getName() + "]");
            }

            if (results == null) {
                results = new ArrayList<TestStepResult>();
            }

            results.add(result);
        }

        public List<TestStepResult> getResults() {
            return results;
        }

        public int getResultCount() {
            return results == null ? 0 : results.size();
        }

        public long getResultAverage() {
            if (results == null) {
                return 0;
            }

            if (results.size() == 1) {
                return results.get(0).getTimeTaken();
            }

            long sum = 0;
            for (TestStepResult result : results) {
                sum += result.getTimeTaken();
            }

            return sum / results.size();
        }
    }

    private class InternalTestSuiteListener extends TestSuiteListenerAdapter {
        public void testStepAdded(TestStep testStep, int index) {
            if (testStep.getTestCase() == testCase) {
                testStep.addPropertyChangeListener(TestStep.NAME_PROPERTY, propertyChangeListener);

                // insert null entry in existing samples
                for (int i = 0; i < samples.size(); i++) {
                    TestSample[] testSamples = samples.get(i);
                    TestSample[] newSamples = new TestSample[testSamples.length + 1];
                    for (int c = 0; c < testSamples.length; c++) {
                        if (c < index) {
                            newSamples[c] = testSamples[c];
                        } else {
                            newSamples[c + 1] = testSamples[c];
                        }
                    }

                    samples.set(i, newSamples);
                }

                fireTableStructureChanged();
            }
        }

        public void testStepRemoved(TestStep testStep, int index) {
            if (testStep.getTestCase() == testCase) {
                testStep.removePropertyChangeListener(propertyChangeListener);

                // remove from samples
                for (int i = 0; i < samples.size(); i++) {
                    TestSample[] testSamples = samples.get(i);
                    TestSample[] newSamples = new TestSample[testSamples.length - 1];
                    for (int c = 0; c < testSamples.length; c++) {
                        if (c < index) {
                            newSamples[c] = testSamples[c];
                        } else if (c > index) {
                            newSamples[c - 1] = testSamples[c];
                        }
                    }

                    samples.set(i, newSamples);
                }

                fireTableStructureChanged();
            }
        }
    }

    private class InternalPropertyChangeListener implements PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent evt) {
            fireTableStructureChanged();
        }
    }

}
